package com.client.koshercook.server;


import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.gwt.user.client.rpc.IsSerializable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.LinkedList;

import javax.jdo.annotations.Element;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import java.lang.Math;

@PersistenceCapable
public class Recipes implements IsSerializable {
    @PrimaryKey
    @Persistent
    private Key key; //the recipe key in data store
    
    @Persistent
    private String site; //the site the recipe is taken from

    @Persistent
    private int idInSite=0; //the id of the recipe in its site
    
    @Persistent
    private String name; // the name of the recipe

    @Persistent
    private short kosher; //dairy,meat or parve.
    
    @Persistent
    private String type; // the type of the recipe: cake,salad,...
    
    @Persistent
    private List<Key> ingredients; //set of ingredients in the recipe
    
    @Persistent
    private List<String> ingredientsText; // the text of ingredients to send to client
    
    @Persistent
    private List<String> instructions; // the instructions of the recipe to send to client

    @Persistent
    private String uploader; // the name of the user who uploaded the recipe
    
    @Persistent
    private int cookTime = 0; //cook time is saved in seconds!
    
    @Persistent
    private List<String> timeInputers; //list of people who inserted cook time
    
    @Persistent
    private double grade = 3; //the output grade will be between 1 and 5,only round numbers. initial value is 5
           
    @Persistent
    private double numOfGraders = 0; //how many people graded this recipe 
    
    @Persistent
    private int numOfFavorites = 0; //how many people added this recipe to their favorites
    
    @Persistent
    @Element(dependent = "true") //it should delete all ingWithAlters when the recipe is deleted from DS
    private List<ingWithAlters> alters;
    //if there are alternative ingredients to an ingredient (in this particular recipe), it will go here
        
    @Persistent
    private List<String> comments=null; // users' comments
    
    @Persistent
    private Integer numOfViews; //how many times people viewed this recipe
    
    @Persistent
    private Integer weekNumber; //increased by 1 every week, while resetting numOfViews
    
    /** constructor used when scanning recipes from the web */
    public Recipes(String site, int idInSite) {
    	alters = new ArrayList<ingWithAlters>();
    	this.site = site;
        this.idInSite = idInSite;
        new ScanRecipe(this);
        this.key = KeyFactory.createKey(Recipes.class.getSimpleName(), getLink());
        if(isValid()) {
        	alters = ingWithAlters.setAltersForGroups(ingredients); //it creates an empty list, and fill with existing groups
        	DataStore.updateRecipeInfo(this);
        	System.out.println("alters is size "+ alters.size());
        }
    }

	/**constructor to create a copy of a recipe*/
    public Recipes(Recipes recipe) {
    	this.site=recipe.getSite();
    	this.idInSite=recipe.getIdInSite();
    	this.key = KeyFactory.createKey(Recipes.class.getSimpleName(), getLink());
    	this.name=recipe.getRecipeName();
    	this.kosher=recipe.getKosher();
    	this.type=recipe.getType();
    	this.ingredients= new ArrayList<Key>();
    	ingredients.addAll(recipe.getIngredients());
    	this.ingredientsText=new ArrayList<String>();
    	ingredientsText.addAll(recipe.getIngredientsText());
    	this.instructions= new ArrayList<String>();
    	instructions.addAll(recipe.getInstructions());
    	this.uploader=recipe.getUploader();
    	this.cookTime=recipe.getCookTime();
    	this.timeInputers=new ArrayList<String>();
    	timeInputers.addAll(recipe.getTimeInputers());
    	this.grade=recipe.getGrade();
    	this.numOfGraders=recipe.getNumOfGraders();
    	this.numOfFavorites=recipe.getNumOfFavorites();
    	this.alters= new ArrayList<ingWithAlters>();
        for(ingWithAlters iwa : recipe.getAlters())
        	this.alters.add(new ingWithAlters(iwa)); 	
        this.comments = new ArrayList<String>();
        if(recipe.getComments()!=null) this.comments.addAll(recipe.getComments());
        this.numOfViews=recipe.getNumOfViews();
        this.weekNumber=recipe.getWeekNumber();
    }
    
    /** constructor used for adding recipes written for our site by users */
    public Recipes(String recipeName, String instructions, String ingredientsText,String type,String uploader) {
    	Globals globals = DataStore.getGlobals();
    	this.name = recipeName;
        this.site = "matkonkasher";
        this.idInSite = globals.getLastRecKosherCook() + 1;
        this.key = KeyFactory.createKey(Recipes.class.getSimpleName(), getLink());
        this.uploader = uploader;
        this.type = type;

		String tmp[] = ingredientsText.split("\n");
		ingredientsText=tmp[0];
		for(int i=1 ; i<tmp.length ; i++) ingredientsText = ingredientsText+"<br/>"+tmp[i];
        this.ingredientsText = new ArrayList<String>();
		for(int i=0 ; i<ingredientsText.length() ; i=i+500) 
			this.ingredientsText.add(ingredientsText.substring(i, Math.min(i+500,ingredientsText.length())));
        
		tmp = instructions.split("\n");
		instructions=tmp[0];
		for(int i=1 ; i<tmp.length ; i++) instructions = instructions+"<br/>"+tmp[i];
		this.instructions = new ArrayList<String>();
		for(int i=0 ; i<instructions.length() ; i=i+500) 
			this.instructions.add(instructions.substring(i, Math.min(i+500,instructions.length())));

		this.ingredients = ScanRecipe.getIngredientsFromText(ingredientsText, "<br/>");
		this.kosher = ScanRecipe.calcKosher(this.ingredients);
		alters = new ArrayList<ingWithAlters>();
		
		if(isValid()) {
        	alters = ingWithAlters.setAltersForGroups(ingredients); //it creates an empty list, and fill with existing groups
        	DataStore.updateRecipeInfo(this);
        	DataStore.addRecipe(this);
        	
        	globals.setLastRecKosherCook(this.idInSite);
    		globals.setNumOfRecipesInDS(globals.getNumOfRecipesInDS()+1);
    		DataStore.updateGlobals(globals);
        	System.out.println("alters is size "+ alters.size());
        }
    }

    public Key getKey() {
        return key;
    }

    public String getSite() {
        return site;
    }
    
    public int getIdInSite() {
        return idInSite;
    }
    
    public String getLink() {
    	if(site.equals("http://www.kipa.co.il/food/matkon/")) return site+idInSite;
    	if(site.equals("http://www.chef-lavan.co.il/tabid/53/RecipeID/")) return site+idInSite+"/Default.aspx";
    	if(site.equals("http://www.rotev.co.il/last_video.aspx?recipe_id=")) return site+idInSite;
    	if(site.equals("http://www.stybel.co.il/recipe_full.php?id=")) return site+idInSite;
    	if(site.equals("matkonkasher")) return site+idInSite;
    	System.out.println("recipes: ERROR! didn't find such site");
    	return ""; //get here = error
    }
    
    public void setRecipeName(String recipeName) {
        this.name = recipeName;
    }
    
    public String getRecipeName() {
        return name;
    }
    
    public void setKosher(short kosher) {
    	this.kosher = kosher;
    }
    
    public short getKosher() {
    	return kosher;
    }
    
    public String getKosherString() {
    	return KosherType.getString(kosher);
    }
            
    public void setType(String type) {
		this.type = type;
	}

	public String getType() {
		return type;
	}

	/**
	 * set the cook time structure (in seconds)
	 */
	public void setCookTime(int cookTime) {
        this.cookTime = cookTime;
    }
    
	/**
	 * get the cook time structure (in seconds)
	 */
    public int getCookTime() {
        return cookTime;
    }
	
	public void setTimeInputers(List<String> timeInputers) {
		this.timeInputers = timeInputers;
	}
	
	public void addTimeInputer(String user) {
		this.timeInputers.add(user);
	}

	public List<String> getTimeInputers() {
		return timeInputers;
	}

	/**
	 * get the average cook time (in minutes)
	 */
    public int getAvarageCookTime() {
        return Math.round((float)getCookTime()/60);
    }
    
    /**
     * check if user hasn't entered a time already. if so, add another cook time (in minutes) to the average
     * @return the new average, or -1 if already inputted by this user
     */
    public int addCookTime(int cookTime,String user) {
    	if(this.timeInputers==null) timeInputers = new ArrayList<String>();
        if(timeInputers.contains(user)) return -1;
    	int res = getCookTime()*this.getTimeInputers().size() + cookTime*60;
        this.addTimeInputer(user);
        res = res / this.getTimeInputers().size();
        setCookTime(res);
        System.out.println("rec: new cook time = "+ this.cookTime + ", shows = "+res/60);
        return Math.round((float)res/60);
    }
    
    public void setIngredients(ArrayList<Key> ingredients) {
        this.ingredients = ingredients;
    }
    
    public void setIngredients(Key ingredients[]) {
    	this.ingredients.clear();
    	for(int i=0; i<ingredients.length; i++)
    		this.ingredients.add(ingredients[i]);
    }
    
    public int getGrade() {
        return (int) Math.round(grade);
    }
    
    public int computeNewGrade(int grade){
		System.out.println("server: computeNewGrade: old grade="+this.grade+" ; old numOfGraders="+numOfGraders+" ; new grade from client = "+grade);
    	this.grade = (this.grade * numOfGraders + grade) / ++numOfGraders;
    	System.out.println("server: computeNewGrade: new grade="+this.grade+" ; new numOfGraders="+numOfGraders);
    	System.out.println("server: computeNewGrade: getGrade is now = "+getGrade());
    	return getGrade();
	}
    
    public int getNumOfGraders() {
    	return (int) numOfGraders;
    }
    
    public List<Key> getIngredients() {
        return ingredients;
    }
    
    public void addIngredient(Key ingredient) {
    	ingredients.add(ingredient);
    }
    
    public boolean hasIngredient(Key ingredient) {
    	for(Key ing : ingredients){
    		if(ing.equals(ingredient))
    			return true;
    	}
    	return false;
    }
    
    public void setIngredientsText(List<String> ingredientsText) {
		this.ingredientsText = ingredientsText;
	}

	public List<String> getIngredientsText() {
		return ingredientsText;
	}
    
	/** update alters in case of a bug, and return ingsText+altersText*/
    public String getIngredientsTextAsString() {
//    	updateAlters(); //TODO not sure if it's good
//		if(alters==null) return ingredientsText;
		//TODO check not groups
    	String str ="";
        for(String x : ingredientsText) str = str + x;
    	if(alters.size()>0) {
	        str = str + "<br><br><b>רכיבים אלטרנטיבים:</b>";
			for(ingWithAlters iwa : alters) str = str + "<br>" + iwa.toString();
    	}
		return str;
	}

	public void setInstructions(List<String> instructions) {
        this.instructions = instructions;
    }
    
    public List<String> getInstructions() {
        return instructions;
    }
    
    public String getInstructionsAsString() {
        String str ="";
        for(String x : instructions) str = str + x;
    	return str;
    }

	public void setUploader(String uploader) {
		this.uploader = uploader;
	}

	public String getUploader() {
		return uploader;
	}
	
	/** checks if recipe has name, kosher, ingredients, instructions */
	public boolean isValid() {
		return name!=null && !name.isEmpty() && kosher!=0 && ingredients!=null && !ingredients.isEmpty() && ingredientsText!=null &&
		!ingredientsText.isEmpty() && instructions!=null && !instructions.isEmpty();
	}

	public List<ingWithAlters> getAlters() {
		return alters;
	}
	
	public List<ingWithAlters> getCopyOfAlters() {
		List<ingWithAlters> res = new ArrayList<ingWithAlters>();
		for(ingWithAlters iwa : alters) 
			res.add(new ingWithAlters(iwa));
		return res;
	}
	
	public boolean hasAnyAlters() {
		return alters.size()!=0;
	}
	
	/** adds an alternative alter to an ingredient original */
	public void addAlternative(Key original, Key alter){
		
		for(ingWithAlters sub : alters){ 					//checks if original already exists in alters
			if((sub.getBaseIng().equals(original)) && (!sub.isGroup())){	//if found original
				sub.addAlter(alter);						//add it to the set
				return;
			}
		}
		//if we are here, original does not exists yet
		alters.add(new ingWithAlters(original,alter));
	}
	
	/** adds a group alternative ingredients */
	public void addAlternatives(List<ingWithAlters> iwa){		
		alters.addAll(iwa);
	}
	
	/** checks if alter is an alternative to the ingredient original */
	public boolean isAlternative(Key original, Key alter){
		for(ingWithAlters sub : alters) {
			if(sub.getBaseIng().equals(original)){
				return sub.isAlter(alter);
			}
		}
		return false;
	}
		
	/** checks if one of alters is an alternative for original */
	public boolean isAlternative(String original, ArrayList<Key> alter){
		for(ingWithAlters sub : alters) {
			if(sub.getBaseIng().equals(original)){
				//found original
				for(Key s : alter)
					if(sub.isAlter(s))
						return true;	//found an alternative
				return false;	//didn't find an alternative
			}
		}
		//didn't found original
		return false;
	}
	
	/** checks if ingredient has an alternative (any alternative) */
	public boolean hasAlternativeFor(Key ingredient) {
		for(ingWithAlters sub : alters) {
			if(sub.getBaseIng().equals(ingredient)){
				return true;
			}
		}
		return false;
	}
	
	/** returns all the alternatives for ingredient. or empty list if none */
	public List<Key> getAllAltersFor(Key ingredient) {
		for(ingWithAlters sub : alters)
			if(sub.getBaseIng().equals(ingredient)){
				return sub.getAllAlters();
			}
		return new ArrayList<Key>();
	}
	
	/** checks if alter is an alternative for any(!) of the ingredients in this recipe */
	public boolean isAlterForOther(Key alter) {
		for(ingWithAlters sub : alters)
			if(sub.getAllAlters().contains(alter))
				return true;
		return false;
	}
	
	/** update the alters list, in case of a bug*/
	public void updateAlters() {
		List<Key> keys = new ArrayList<Key>();
		keys.add(this.ingredients.get(0));
		List<RecipeInfo> ris = DataStore.getIngredientsFromKeys(keys).get(0).getInRecipes();
		for(RecipeInfo ri : ris) {
			if(ri.getLink().equals(this.getLink())) {
				System.out.println("REC: bef alters size = "+alters.size()+" , ri alt size = "+ri.getAlters().size());
				this.alters = ri.getAlters();
				System.out.println("REC: aft alters size = "+alters.size());
				break;
			}
		}
	}
	
	/**get a list with all ingredients and alters. the list might have duplicates*/
	public List<Key> getListOfIngredientsAndAlters() {
		List<Key> res = new ArrayList<Key>();
		res.addAll(ingredients);
		for(ingWithAlters iwa : alters) res.addAll(iwa.getAllAlters());
		return res;
	}
	
	/**get a list with all ingredients and alters. the set have no duplicates*/
	public Set<Key> getSetOfIngredientsAndAlters() {
		Set<Key> res = new HashSet<Key>();
		res.addAll(ingredients);
		for(ingWithAlters iwa : alters) res.addAll(iwa.getAllAlters());
		return res;
	}

	/**set the numOfFavorites field*/
	public void setNumOfFavorites(int numOfFavorites) {
		this.numOfFavorites = numOfFavorites;
	}
	
	/**increase the numOfFavorites field by 1*/
	public void increaseNumOfFavorites() {
		this.numOfFavorites = numOfFavorites++;
	}
	
	/**decrease the numOfFavorites field by 1*/
	public void decreaseNumOfFavorites() {
		this.numOfFavorites = numOfFavorites--;
	}

	/**get how many people added this recipe to their favorites*/
	public int getNumOfFavorites() {
		return numOfFavorites;
	}

	public void setComments(List<String> comments) {
		this.comments = comments;
	}

	/** return list of comments on recipe. each comment is a string of: uploader+"\u6000"+comment */
	public List<String> getComments() {
		return comments;
	}

	public void addComment(String comment, String uploader) {
		if(this.comments==null) this.comments = new ArrayList<String>();
		this.comments.add(uploader+"\u6000"+comment);
		
	}

	public void setNumOfViews(Integer numOfViews) {
		this.numOfViews = numOfViews;
	}

	public Integer getNumOfViews() {
		if(this.numOfViews==null) setNumOfViews(0);
		return numOfViews;
	}
	
	/**
	 * increase NumOfViews by 1
	 */
	public void increaseNumOfViews() {
		if(this.numOfViews==null) setNumOfViews(0);
		this.numOfViews++;
	}

	public void setWeekNumber(Integer weekNumber) {
		this.weekNumber = weekNumber;
	}

	public Integer getWeekNumber() {
//		if(this.weekNumber==null) setWeekNumber(0);
		return weekNumber;
	}
	
	
}